from datetime import datetime
from typing import Optional, Any

from fastapi import Request

from .device_crud import DeviceCRUD
from .menu_crud import MenuCRUD
from .user_crud import UserCRUD
from ..config import settings, AuthType
from ..core.custom_exception import AuthenticateException
from ..core.security import (
    create_access_token,
    create_refresh_token,
    create_token_version,
    decode_token, TokenData,
)
from ..models import User, Device
from ..models.base import IdType
from ..schemas.base_schema import CacheKeyPrefixConst, LoginAccountType
from ..schemas.device_schema import DeviceCreate, DeviceUpdate
from ..schemas.user_schema import UserLoginOut, CurrentUser, UserLogin
from ..utils.device_info import get_device_info


class AuthCRUD:
    def __init__(self, user_crud:UserCRUD,menu_curd:MenuCRUD,device_crud:DeviceCRUD,request:Request):
        self.user_crud = user_crud
        self.menu_curd = menu_curd
        self.device_crud = device_crud
        self.request = request

    async def login_by_account(self, login_in: UserLogin) -> UserLoginOut:
        # 获取当前登录用户
        user = await self.user_crud.validate_user(login_in=login_in)

        # 获取设备信息
        device_info = get_device_info(self.request)
        # 获取当前设备
        current_device = self.get_current_device(user,device_info)
        # 如果当前设备为空，则添加当前设备,否则更新当前设备登录时间
        if current_device is None:
            create_device = DeviceCreate(user_id=user.id, last_login_datetime= datetime.now(), **device_info)
            current_device = await self.device_crud.create(obj=create_device)
        else:
            update_device = DeviceUpdate(**{"id":current_device.id,"last_login_datetime":datetime.now()})
            current_device = await self.device_crud.update(obj=update_device)

        return await self._execute_login(user=user,account=login_in.account,account_type=login_in.account_type,current_device=current_device)

    async def _execute_login(self,user: User,account:str,account_type:LoginAccountType,current_device:Device) -> UserLoginOut:
        current_user = CurrentUser.model_validate(user)

        if settings.AUTH_TYPE == AuthType.MENU:
            auth_keys = await self.menu_curd.get_auth_keys(current_user.id)
            current_user.auth_keys = list(auth_keys)

        # 生成token版本号
        token_version = create_token_version()

        # 生成 token
        token_data= TokenData(**{
            "sub": str(current_user.id),
            "account": account,
            "device_id": str(current_device.id),
            "device_type": current_device.type,
            "account_type": account_type,
            "token_version": token_version,
            "auth_keys": current_user.auth_keys,
        })
        access_token = create_access_token(token_data)
        # 生成刷新Token
        refresh_token = create_refresh_token()
        # 缓存token版本号 和 刷新Token
        await self.user_crud.cache.set(
            key=f"{CacheKeyPrefixConst.TOKEN_VERSION_CACHE_PREFIX.value}{current_user.id}{current_device.id}",
            value=token_version,
            expire=settings.ACCESS_TOKEN_EXPIRE_MINUTES,
        )
        await self.user_crud.cache.set(
            key=f"{CacheKeyPrefixConst.REFRESH_TOKEN_CACHE_PREFIX.value}{current_user.id}{current_device.id}",
            value=refresh_token,
            expire=settings.REFRESH_TOKEN_EXPIRE_MINUTES,
        )

        self.request.state.current_user = current_user
        self.request.state.token_data = token_data

        return UserLoginOut(access_token=access_token, refresh_token=refresh_token)

    async def refresh_token(self,token:str, refresh_token: str):
        payload = decode_token(token)
        user_id = payload.get("sub")
        account_type = payload.get("account_type")
        device_id = payload.get("device_id")

        # 验证刷新Token
        refresh_token_cache = await self.user_crud.cache.get(key=f"{CacheKeyPrefixConst.REFRESH_TOKEN_CACHE_PREFIX.value}{user_id}{device_id}")
        if refresh_token != refresh_token_cache:
            raise AuthenticateException(message="刷新Token无效")

        user = await self.user_crud.validate_by_id(id=IdType(user_id))

        # 获取设备信息
        device_info = get_device_info(self.request)
        # 获取当前设备
        current_device = self.get_current_device(user,device_info)
        # 如果当前设备不存在或者设备指纹与Token中的指纹不一致，则抛出异常
        if current_device is None or str(current_device.id) != device_id:
            raise AuthenticateException(message="当前设备异常")

        current_user = CurrentUser.model_validate(user)

        if settings.AUTH_TYPE == AuthType.MENU:
            auth_keys = await self.menu_curd.get_auth_keys(current_user.id)
            current_user.auth_keys = list(auth_keys)

        # 生成token版本号
        new_token_version = payload.get("token_version") + 1

        # 生成 新token
        token_data= TokenData(**payload)
        token_data.token_version = new_token_version
        token_data.auth_keys = current_user.auth_keys
        new_access_token = create_access_token(token_data)

        # 生成 新刷新Token
        new_refresh_token = create_refresh_token()

        # 缓存 新token版本号 和 新刷新Token
        await self.user_crud.cache.set(
            key=f"{CacheKeyPrefixConst.TOKEN_VERSION_CACHE_PREFIX.value}{user_id}{device_id}",
            value=new_token_version,
            expire=settings.ACCESS_TOKEN_EXPIRE_MINUTES,
        )

        await self.user_crud.cache.set(
            key=f"{CacheKeyPrefixConst.REFRESH_TOKEN_CACHE_PREFIX.value}{user_id}{device_id}",
            value=new_refresh_token,
            expire=settings.REFRESH_TOKEN_EXPIRE_MINUTES,
        )

        self.request.state.current_user = current_user
        self.request.state.token_data = token_data

        return UserLoginOut(
            access_token=new_access_token, refresh_token=new_refresh_token
        )

    def get_current_device(self,user:User,device_info:dict[str,Any]) -> Optional[Device] :
        # 获取前端传递的设备指纹
        # 前端需要在请求头中添加: X-Device-Fingerprint: <指纹值>
        device_fp = self.request.headers.get("X-Device-Fingerprint", None)
        if device_fp:
            device_info["device_fingerprint"] = device_fp
        current_device = next((item for item in user.devices if item.device_fingerprint == device_info["device_fingerprint"]), None)
        return current_device
